using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;

using Amazon.SimpleDB;
using Amazon.SimpleDB.Model;
using Amazon.SimpleDB.Util;

using PennyStorm.Util;

namespace PennyStorm.Controllers
{
    public class AuctionController : Controller
    {
        //
        // GET: /Auction/        
        public ActionResult Details(string id)
        {
            //
            // Get attributes for this auction item
            //
            AmazonSimpleDBClient sdb = AmazonSimpleDBFactory.Instance.Client;

            GetAttributesRequest getAttributes = AmazonSimpleDBFactory.Instance.MakeGetAttributesRequest(true);            
            getAttributes.ItemName = id;

            GetAttributesResponse response = sdb.GetAttributes(getAttributes);

            ViewData["ItemId"] = id;
            ViewData["ItemAttributes"] = response.GetAttributesResult.Attribute;
                        
            return View();
        }

        public string Winner(string id)
        {
            string winner = AmazonSimpleDBFactory.Instance.GetStringValue(id, Properties.Settings.Default.item_WinnerKey);

            if (string.IsNullOrEmpty(winner))
            {
                winner = "No bidders yet";
            }

            return winner;
        }

        public int Time(string id)
        {
            int time = AmazonSimpleDBFactory.Instance.GetIntegerValue(id, Properties.Settings.Default.item_TimeKey);

            return time;
        }

        public string Bids(string id)
        {
            //
            // Get all bids for this item; this list is not super-critical, so we will not to do a consistent read here
            //
            GetAttributesRequest getAttributes = AmazonSimpleDBFactory.Instance.MakeGetAttributesRequest(false);
                        
            getAttributes.ItemName = id;
            getAttributes.AttributeName.Add(Properties.Settings.Default.item_BidsKey);
            
            GetAttributesResponse response = AmazonSimpleDBFactory.Instance.Client.GetAttributes(getAttributes);

            List<Bid> bids = new List<Bid>();
            if (response.IsSetGetAttributesResult())
            {
                foreach (Amazon.SimpleDB.Model.Attribute attribute in response.GetAttributesResult.Attribute)
                {
                    string[] parts = attribute.Value.Split(new char[] { ',' });
                    bids.Add(new Bid(parts[0], long.Parse(parts[1])));
                }
            }

            //
            // Now that we have our list of bids, sort in descending order based on the timestamp
            //              
            bids.Sort(new CompareBid());

            //
            // Finally, encode in JSON
            //
            System.Text.StringBuilder jsonBuilder = new System.Text.StringBuilder();
            
            jsonBuilder.Append("[");

            //
            // Just do the top 10
            //
            int all = bids.Count > 10 ? 10 : bids.Count;
            for (int i = 0; i < all;  i++)
            {
                jsonBuilder.AppendFormat("\"{0}\"", bids[i].bidder);

                if ((i + 1) < all)
                {
                    jsonBuilder.Append(",");
                }
            }

            jsonBuilder.Append("]");           

            return jsonBuilder.ToString();
        }

        public int Price(string id)
        {
            //
            // Get the current price of the item from Amazon SimpleDB - this has to be a consistent call because we need
            // the absolute latest value.
            //
            int price = AmazonSimpleDBFactory.Instance.GetIntegerValue(id, Properties.Settings.Default.item_PriceKey);

            return price;
        }

        public bool Bid(string id)
        {
            bool success = false;

            //
            // We need to do 4 things here 
            //  1.  Make sure our user has enough bids in their account
            //  2.  If #1 is true, decrement the user 1 bid
            //  3.  Place the bid
            //  4.  If #3 fails, credit the user back their bid
            //
            //  #1 and #2 have to use consistent read and write
            
            //
            // First thing to check is how many bids this user has; we need to do a consistent read here because we have to know the 
            // actual number; otherwise this user may be getting free bids
            //            
            int currentNumBids = GetNumBids();

            if (currentNumBids > 0)
            {
                //
                // OK, the user has bids, so they can place this bid. But, we have to be careful here, if anything goes wrong in terms
                // of the bid, we need to credit the user their bid again.
                //
                
                //
                // This is the number of bids we will have after this bid
                //
                int afterBidNumBids = currentNumBids - 1;
                
                //
                // We need an update condition - we are only going to update if the value is currentNumBids as we would expect from
                // our consistent read
                //
                success = DebitAccount(currentNumBids, afterBidNumBids);
                
                if (success)
                {
                    //
                    // At this point we have debited the user's account; if there is a problem with placing the bid, we will need to roll back
                    // the debit.
                    //
                
                    //
                    // If this was successful, put in our bid for the item
                    //
                    success = PlaceBid(id);
                    
                    //
                    // If we were not successful,then we have to credit the account back
                    //
                    if (!success)
                    {
                        PutAttributesRequest putAttributes = AmazonSimpleDBFactory.Instance.MakePutAttributesRequestForUser(this);
                        putAttributes.Attribute.Add(AmazonSimpleDBFactory.Instance.MakeReplaceableAttribute(Properties.Settings.Default.user_NumBidsKey, currentNumBids.ToString(), true));

                        AmazonSimpleDBFactory.Instance.Client.PutAttributes(putAttributes);
                    }
                }              
            }
            
            return success;
        }

        private bool PlaceBid(string id)
        {
            bool success = false;

            try
            {
                //
                // We place our bid by doing the following:
                //
                // Get the following through a consistent read
                //
                //  1. Current price
                //  2. Current time                                
                //
                // With those values, do the following
                //  
                //  1. Update price by 1(conditionally based on the current price)
                //  2. Update time by a random amount (between 0 and 5)
                //
                // Set the user as the current winner and add their bid (along with a timestamp to the list of bids)
                //

                int currentPrice = 0;
                int newPrice = 0;
                AmazonSimpleDBFactory.Instance.ConsistentSwapInteger(id, Properties.Settings.Default.item_PriceKey, true, 1, out currentPrice, out newPrice);

                int currentTime = 0;
                int newTime = 0;
                int additionalTime = new Random().Next(5);
                success = AmazonSimpleDBFactory.Instance.ConsistentSwapInteger(id, Properties.Settings.Default.item_TimeKey, true, additionalTime, out currentTime, out newTime);

                if (!success)
                {
                    //
                    // Set our price back if there was a failure
                    //
                    AmazonSimpleDBFactory.Instance.SetIntegerValue(id, Properties.Settings.Default.item_PriceKey, currentPrice);
                }
                else
                {
                    PutAttributesRequest putAttributes = AmazonSimpleDBFactory.Instance.MakePutAttributesRequest();                    
                    putAttributes.ItemName = id;

                    //
                    // Set this user as the current winner
                    //                    
                    putAttributes.Attribute.Add(AmazonSimpleDBFactory.Instance.MakeReplaceableAttribute(Properties.Settings.Default.item_WinnerKey, HttpContext.User.Identity.Name, true));

                    //
                    // Add this to the list of bids
                    //                    
                    string bidString = string.Format("{0},{1}", HttpContext.User.Identity.Name, DateTime.Now.Ticks);
                    putAttributes.Attribute.Add(AmazonSimpleDBFactory.Instance.MakeReplaceableAttribute(Properties.Settings.Default.item_BidsKey, bidString, false));
                    
                    //
                    // Mark this auction as active, this makes it a lot easier for our timer service to spot this auction
                    //
                    putAttributes.Attribute.Add(AmazonSimpleDBFactory.Instance.MakeReplaceableAttribute(Properties.Settings.Default.item_ActiveKey, "1", true));

                    AmazonSimpleDBFactory.Instance.Client.PutAttributes(putAttributes);                    
                }

                success = true;
            }
            catch
            {
                //
                // We error on the side of throwing out your bid so that you don't waste it - if someone else snuck their bid in before yours,
                // we fail your bid. Since this is a reverse auction, this is actually to your benefit. If someone else has bid and 'kep the auction alive'
                // then there is no need for you to bid as well.
                //
            }

            return success;
        }

        private bool DebitAccount(int currentNumBids, int afterBidNumBids)
        {
            bool success = false;
            try
            {
                UpdateCondition updateCondition = new UpdateCondition();
                updateCondition.Name = Properties.Settings.Default.user_NumBidsKey;
                updateCondition.Value = currentNumBids.ToString();

                //
                // 'Place' the bid by first decrementing the account
                //
                PutAttributesRequest putAttributes = AmazonSimpleDBFactory.Instance.MakePutAttributesRequestForUser(this);

                putAttributes.Expected = updateCondition;
                putAttributes.Attribute.Add(AmazonSimpleDBFactory.Instance.MakeReplaceableAttribute(Properties.Settings.Default.user_NumBidsKey, afterBidNumBids.ToString(), true));

                AmazonSimpleDBFactory.Instance.Client.PutAttributes(putAttributes);

                success = true;
            }
            catch
            {

            }

            return success;
        }

        public int GetNumBids()
        {
            GetAttributesRequest getAttributes = AmazonSimpleDBFactory.Instance.MakeGetAttributesRequestForUser(true, this);
            getAttributes.AttributeName.Add(Properties.Settings.Default.user_NumBidsKey);

            GetAttributesResponse response = AmazonSimpleDBFactory.Instance.Client.GetAttributes(getAttributes);

            int currentNumBids = 0;
            if (response.IsSetGetAttributesResult())
            {
                if (response.GetAttributesResult.Attribute.Count > 0)
                {
                    //
                    // We only care about the first one
                    //
                    currentNumBids = Int32.Parse(response.GetAttributesResult.Attribute[0].Value);
                }
            }

            return currentNumBids;
        }
    }

    public class Bid
    {
        public string bidder;
        public long timestamp;

        public Bid(string bidder, long timestamp)
        {
            this.bidder = bidder;
            this.timestamp = timestamp;
        }
    }

    public class CompareBid : System.Collections.Generic.IComparer<Bid>
    {
        public int Compare(Bid x, Bid y)
        {
            if (x.timestamp < y.timestamp)
            {
                return -1;
            }
            else if (x.timestamp == y.timestamp)
            {
                return 0;
            }
            else
            {
                return 1;
            }
        }
    }
}
